/*
* 2018/03/28
* 模仿贪吃蛇游戏
* */
(function (window) {
    var sounds = [
        './sounds/fail1.wav',
        './sounds/fail2.wav',
        './sounds/success.wav'
    ];

    function Snake(options) {
        return new Snake.fn.init(options)
    }

    Snake.fn = Snake.prototype = {
        constructor: Snake,

        init: function (options) {

            // 如果参数不存在让他等于空对象 否则下面的赋值会出问题
            options = options || {}

            this.options = options

            this.canvas = options.canvas || document.querySelector('#my-canvas')
            this.ctx = this.canvas.getContext('2d')
            this.snakes = options.snakes || [[0, 0], [1, 0], [2, 0]]
            this.item = options.item || 30
            this.rowCeils = (options.rowCeils || 40) > 60 ? 60 : (options.rowCeils || 40)
            this.columeCeils = this.rowCeils / 2
            this.speed = 45 - this.rowCeils / 2
            this.targetItem = []  //目标数组
            this.firstItem = []
            this.lastItem = []
            this.lastSnakes = []
            this.directive = options.directive || 39
            this.score = 0
            this.timer = null
            this.isStart = false
            this.isPause = false
            this.soundDom = options.soundDom || document.querySelector('#sound')

            this.drawGrid().randomTarget().drawSnakes().initSounds().addEventHandle()

            return this
        },

        initSounds: function () {
            this.soundDom.src = ''
            return this
        },

        move: function () {
            var newItem = [],
                lastItem = this.snakes[this.snakes.length - 1];

            this.firstItem = this.snakes[0]
            this.snakes.splice(0, 1)
            switch (this.directive) {
                case 37:
                    newItem = [lastItem[0] - 1, lastItem[1]]
                    break;
                case 38:
                    newItem = [lastItem[0], lastItem[1] - 1]
                    break;
                case 39:
                    newItem = [lastItem[0] + 1, lastItem[1]]
                    break;
                case 40:
                    newItem = [lastItem[0], lastItem[1] + 1]
                    break;
            }
            this.lastSnakes = this.snakes
            this.snakes.push(newItem)
            return this
        },

        drawGrid: function () {
            var width = this.item * this.rowCeils,
                height = this.item * this.columeCeils,
                i;

            this.canvas.width = width
            this.canvas.height = height

            for (i = 0; i <= this.columeCeils; i++) {
                this.stroke(0, i * this.item, width, i * this.item)
            }

            for (i = 0; i <= this.rowCeils; i++) {
                this.stroke(i * this.item, 0, i * this.item, height)
            }

            return this
        },

        drawSnakes: function () {
            this.snakes.forEach((v) => {
                this.drawRect(v[0], v[1])
            })

            return this
        },

        soundTips: function (index) {
            this.soundDom.src = sounds[index]
            this.soundDom.play()

            return this
        },

        toast: function () {
            var toastDom = document.createElement('div')

            toastDom.style.position = 'absolute'
            toastDom.style.left = '50%'
            toastDom.style.top = '50%'
            toastDom.style.padding = '10px 20px'
            toastDom.style.backgroundColor = '#ddd'
            toastDom.style.transform = 'translate(-50%, -50%)'
            toastDom.style.border = '1px solid #000'
            toastDom.style.boxShadow = '1px 1px #cdcdcd'
            toastDom.innerHTML = '<p>游戏结束！</p><p>得分为：' + this.score + '</p>'
            document.querySelector('#l-canvas-box').appendChild(toastDom)

            setTimeout(() => {
                document.querySelector('#l-canvas-box').removeChild(toastDom)
                this.init(this.options)
            }, 3000)

            return this
        },

        render: function () {
            if (this.isPause) return

            this.move()

            if (this.isEnd()) return

            this.ctx.clearRect(0, 0, this.item * this.rowCeils, this.item * this.columeCeils)

            this.drawGrid().handleSuccess().drawSnakes()

            setTimeout(() => {
                this.render()
            }, this.speed * (1000 / 60))

            return this
        },

        isEnd: function () {
            var testArr = this.snakes.slice(0, this.snakes.length - 1),
                lastItem = this.snakes[this.snakes.length - 1];

            if (
                this.positionIsExist(lastItem, testArr)
                || lastItem[0] < 0
                || lastItem[0] >= this.rowCeils
                || lastItem[1] < 0
                || lastItem[1] >= this.columeCeils
            ) {
                this.isStart = false
                this.soundTips(Math.round(Math.random())).toast()
                return true
            }

            return false
        },

        handleSuccess: function () {

            this.lastItem = this.snakes[this.snakes.length - 1]

            if (this.lastItem[0] == this.targetItem[0] && this.lastItem[1] == this.targetItem[1]) {
                this.addItem().randomTarget()
            }

            return this
        },

        addItem: function () {
            this.snakes.unshift(this.firstItem)
            this.score++

            // 控制速度 吃的越多 速度越快
            this.speed = this.speed < 5 ? 5 : this.speed - 1
            this.soundTips(2)

            return this
        },

        stroke: function (x1, y1, x2, y2, color) {
            this.ctx.beginPath()
            this.ctx.strokeStyle = color || '#cdcdcd'
            this.ctx.moveTo(x1, y1)
            this.ctx.lineTo(x2, y2)
            this.ctx.stroke()
            this.ctx.closePath()
            return this
        },

        addEventHandle: function () {

            // 使用on并非addEventListener 避免事件重复绑定

            document.body.onkeydown = (e) => {
                e = e || window.event

                // 如果没开始直接return
                if (!this.isStart) return

                // 如果按得空格 切换暂停和播放
                if (e.keyCode == 32) {
                    this.isPause = !this.isPause
                    if (!this.isPause) {
                        this.render()
                    }

                    return
                }

                // 按得方向键 切换前进方向
                if (e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40) {

                    // 如果按得键的方向和正在行进的方向相同直接return
                    if (this.directive == e.keyCode) return

                    //如果按得键的方向和正在行进的方向正相反 return
                    if (Math.abs(this.directive - e.keyCode) == 2) return

                    this.directive = e.keyCode
                }
            }

            document.querySelector('#f-start').onclick = () => {
                if (this.isStart) return
                this.render()
                this.isStart = true
            }
        },

        positionIsExist: function (testArr, targetArr) {
            return targetArr.includes(testArr)
        },

        drawRect: function (x, y, color) {
            this.ctx.beginPath()
            this.ctx.fillRect(x * this.item + 1, y * this.item + 1, this.item - 2, this.item - 2)
            this.ctx.fillStyle = color || '#000'
            this.ctx.closePath()
        },

        randomTarget: function () {
            this.targetItem = [Math.floor(Math.random() * this.rowCeils), Math.floor(Math.random() * this.columeCeils)]

            if (this.positionIsExist(this.targetItem, this.lastSnakes)) {
                this.randomTarget()
            } else {
                this.drawBlinkTarget()
            }

            return this
        },

        drawBlinkTarget: function () {
            var color = '',
                flag = true;

            this.timer = setInterval(() => {
                flag = !flag
                color = flag ? '#000' : "#fff"
                this.drawRect(this.targetItem[0], this.targetItem[1], color)
            }, this.speed * 1000 / 60)

            return this
        }
    }

    // 将init的原型对象绑定在Snake的原型对象上，这样实例化出来的实例对象才能调用Snake原型对象上的方法
    Snake.fn.init.prototype = Snake.fn

    window.Snake = Snake
})(window)